package com.xiyuan.smartutils;

import com.xiyuan.smartutils.constants.CodeConstants;
import com.xiyuan.smartutils.constants.SignConstants;
import com.xiyuan.smartutils.exceptions.IORuntimeException;
import com.xiyuan.smartutils.reflect.Unsafe;
import com.xiyuan.smartutils.streams.BOMInputStream;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.zip.GZIPInputStream;

/**
 * 流工具类，注意，本类中的方法都没有对输入输出流作flush和close操作，需要业务层实现
 *
 * @version v1.0.0 @author lgz 2017-8-27 新建与整理
 */

@SuppressWarnings("all")
public final class IoUtils implements CodeConstants, SignConstants {
    
    /**
     * 默认缓存大小 8192
     */
    public static final int DEFAULT_BUFFER_SIZE = 2 << 12;
    /**
     * 默认中等缓存大小 16384
     */
    public static final int DEFAULT_MIDDLE_BUFFER_SIZE = 2 << 13;
    /**
     * 默认大缓存大小 32768
     */
    public static final int DEFAULT_LARGE_BUFFER_SIZE = 2 << 14;
    
    /**
     * 数据流末尾
     */
    public static final int EOF = -1;
    
    
    private IoUtils() {}
    
    public static byte[] read(InputStream input) throws IOException{
        return getBytes(input);
    }
    public static byte[] read(InputStream input, int len) throws IOException{
        return getBytes(input, len);
    }
    /******************************************************************/
    // Stream与Reader
    /******************************************************************/
    
    /**
     * 把Stream转化为Reader
     */
    public static BufferedReader toReader(InputStream input)  {
        return new BufferedReader(new InputStreamReader(input));
    }
    
    /**
     * 把Stream转化为Reader
     */
    public static BufferedReader toReader(InputStream input, String encoding)  {
        return new BufferedReader(new InputStreamReader(input, Charset.forName(encoding)));
    }
    
    /**
     * 把Stream转化为Reader
     */
    public static BufferedReader toReader(InputStream input, Charset charset)  {
        if (charset == null) {return new BufferedReader(new InputStreamReader(input));}
        return new BufferedReader(new InputStreamReader(input, charset));
    }
    
    /**
     * 把Stream转化为Reader编码为UTF-8
     */
    public static BufferedReader toReaderUTF8(InputStream input) {
        return new BufferedReader(new InputStreamReader(input, _UTF_8_C_));
    }
    
    /**
     * 从{@link BOMInputStream}中获取Reader
     *
     * @param in {@link BOMInputStream}
     * @return {@link BufferedReader}
     */
    public static BufferedReader toReader(BOMInputStream in) {
        return toReader(in, in.getCharset());
    }
    /******************************************************************/
    // Stream与String
    /******************************************************************/
    
    /**
     * 把Stream化为Reader，再读成UTF-8字符串
     */
    public static String readStringUTF8(InputStream stream) throws IOException {
        return readString(stream, _UTF_8_C_);
    }
    
    /**
     * 把Stream化为Reader，再读成GBK字符串
     */
    public static String readStringGBK(InputStream stream) throws IOException {
        return readString(stream, _GBK_C_);
    }
    
    /**
     * 把Stream化为Reader，再读成字符串
     */
    public static String readString(InputStream input, String encoding) throws IOException {
        Charset charset = Charset.forName(encoding);
        return readString(input, charset);
    }
    
    /**
     * 把Stream化为Reader，再读成字符串
     */
    public static String readString(InputStream input) throws IOException {
        Asserts.nonNull(input, _INPUT_);
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(input));
            StringWriter writer = new StringWriter();
            
            char[] buffer = new char[KiB];
            int len = 0;
            while ((len = reader.read(buffer)) > 0) {
                writer.write(buffer, 0, len);
            }
            
            return writer.toString();
        }
        finally {
            input.close();
        }
    }
    
    /**
     * 把Stream化为Reader，再读成字符串
     */
    public static String readString(InputStream input, Charset charset) throws IOException {
        Asserts.nonNull(input, _INPUT_);
        Asserts.nonNull(charset, _CHARSET_);
        
        try {
            BufferedReader reader = toReader(input, charset);
            StringWriter writer = new StringWriter();
            
            char[] buffer = new char[KiB];
            int len = 0;
            while ((len = reader.read(buffer)) > 0) {
                writer.write(buffer, 0, len);
            }
            
            return writer.toString();
        }
        finally {
            input.close();
        }
    }
    
    /**
     * 对Gzip的流进行解析读取字符串
     */
    public static String getStringGzip(InputStream input, String encoding) throws IOException {
        Asserts.nonNull(input, _INPUT_);
        
        GZIPInputStream gzip = new GZIPInputStream(input);
        
        try {
            return readString(gzip, encoding);
        }
        finally {
            gzip.close();
            input.close();
        }
    }
    
    /**
     * 字符串转流，GBK编码，内容为null时返回null
     *
     * @param content 内容
     * @return ByteArrayInputStream
     */
    public static ByteArrayInputStream toStreamGBK(String content) {
        return toStream(content, _GBK_C_);
    }
    
    /**
     * 字符串转流，UTF-8编码，内容为null时返回null
     *
     * @param content 内容
     * @return ByteArrayInputStream
     */
    public static ByteArrayInputStream toStreamUTF8(String content) {
        return toStream(content, _UTF_8_C_);
    }
    
    /**
     * 字符串转流，内容为null时返回null
     *
     * @param content  内容
     * @param encoding 编码
     * @return ByteArrayInputStream
     */
    public static ByteArrayInputStream toStream(String content, String encoding) {
        if (content == null) {return null;}
        
        try {
            return new ByteArrayInputStream(content.getBytes(encoding));
        }
        catch (UnsupportedEncodingException e) {
            throw Asserts.exception("非法的字符编码[" + encoding + "]", e);
        }
    }
    
    /**
     * 字符串转流，内容为null时返回null
     *
     * @param content 内容
     * @param charset 字符集
     * @return ByteArrayInputStream
     */
    public static ByteArrayInputStream toStream(String content, Charset charset) {
        Asserts.nonNull(content, "content");
        Asserts.nonNull(charset, _CHARSET_);
        
        return new ByteArrayInputStream(content.getBytes(charset));
    }
    
    /**
     * 文件转为{@link FileInputStream}
     *
     * @param file 文件
     * @return {@link FileInputStream}
     */
    public static FileInputStream toStream(File file) {
        try {
            return new FileInputStream(file);
        } catch (FileNotFoundException e) {
            throw new IORuntimeException(e);
        }
    }
    
    /**
     * byte[] 转为{@link ByteArrayInputStream}
     *
     * @param content 内容bytes
     * @return 字节流
     * 
     */
    public static ByteArrayInputStream toStream(byte[] content) {
        if (content == null) {
            return null;
        }
        return new ByteArrayInputStream(content);
    }
    
    /**
     * {@link ByteArrayOutputStream}转为{@link ByteArrayInputStream}
     *
     * @param out {@link ByteArrayOutputStream}
     * @return 字节流
     */
    public static ByteArrayInputStream toStream(ByteArrayOutputStream out) {
        if (out == null) {
            return null;
        }
        return new ByteArrayInputStream(out.toByteArray());
    }
    /**
     * 获得一个文件读取器
     *
     * @param path    文件Path
     * @param charset 字符集
     * @return BufferedReader对象
     * @throws IORuntimeException IO异常
     * 
     */
    public static BufferedReader getReader(Path path, Charset charset) throws IORuntimeException {
        return toReader(getInputStream(path), charset);
    }
    /**
     * 获得输入流
     *
     * @param path Path
     * @return 输入流
     * @throws IORuntimeException 文件未找到
     * 
     */
    public static BufferedInputStream getInputStream(Path path) throws IORuntimeException {
        final InputStream in;
        try {
            in = Files.newInputStream(path);
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
        return IoUtils.toBuffered(in);
    }
    /**
     * 转换为{@link BufferedInputStream}
     *
     * @param in {@link InputStream}
     * @return {@link BufferedInputStream}
     * 
     */
    public static BufferedInputStream toBuffered(InputStream in) {
        Asserts.nonNull(in, "InputStream must be not null!");
        return (in instanceof BufferedInputStream) ? (BufferedInputStream) in : new BufferedInputStream(in);
    }
    
    /**
     * 转换为{@link BufferedInputStream}
     *
     * @param in         {@link InputStream}
     * @param bufferSize buffer size
     * @return {@link BufferedInputStream}
     * 
     */
    public static BufferedInputStream toBuffered(InputStream in, int bufferSize) {
        Asserts.nonNull(in, "InputStream must be not null!");
        return (in instanceof BufferedInputStream) ? (BufferedInputStream) in : new BufferedInputStream(in, bufferSize);
    }
    
    /**
     * 转换为{@link BufferedOutputStream}
     *
     * @param out {@link OutputStream}
     * @return {@link BufferedOutputStream}
     * 
     */
    public static BufferedOutputStream toBuffered(OutputStream out) {
        Asserts.nonNull(out, "OutputStream must be not null!");
        return (out instanceof BufferedOutputStream) ? (BufferedOutputStream) out : new BufferedOutputStream(out);
    }
    
    /**
     * 转换为{@link BufferedOutputStream}
     *
     * @param out        {@link OutputStream}
     * @param bufferSize buffer size
     * @return {@link BufferedOutputStream}
     * 
     */
    public static BufferedOutputStream toBuffered(OutputStream out, int bufferSize) {
        Asserts.nonNull(out, "OutputStream must be not null!");
        return (out instanceof BufferedOutputStream) ? (BufferedOutputStream) out : new BufferedOutputStream(out, bufferSize);
    }
    
    /**
     * 转换为{@link BufferedReader}
     *
     * @param reader {@link Reader}
     * @return {@link BufferedReader}
     * 
     */
    public static BufferedReader toBuffered(Reader reader) {
        Asserts.nonNull(reader, "Reader must be not null!");
        return (reader instanceof BufferedReader) ? (BufferedReader) reader : new BufferedReader(reader);
    }
    
    /**
     * 转换为{@link BufferedReader}
     *
     * @param reader     {@link Reader}
     * @param bufferSize buffer size
     * @return {@link BufferedReader}
     * 
     */
    public static BufferedReader toBuffered(Reader reader, int bufferSize) {
        Asserts.nonNull(reader, "Reader must be not null!");
        return (reader instanceof BufferedReader) ? (BufferedReader) reader : new BufferedReader(reader, bufferSize);
    }
    
    /**
     * 转换为{@link BufferedWriter}
     *
     * @param writer {@link Writer}
     * @return {@link BufferedWriter}
     * 
     */
    public static BufferedWriter toBuffered(Writer writer) {
        Asserts.nonNull(writer, "Writer must be not null!");
        return (writer instanceof BufferedWriter) ? (BufferedWriter) writer : new BufferedWriter(writer);
    }
    
    /**
     * 转换为{@link BufferedWriter}
     *
     * @param writer     {@link Writer}
     * @param bufferSize buffer size
     * @return {@link BufferedWriter}
     * 
     */
    public static BufferedWriter toBuffered(Writer writer, int bufferSize) {
        Asserts.nonNull(writer, "Writer must be not null!");
        return (writer instanceof BufferedWriter) ? (BufferedWriter) writer : new BufferedWriter(writer, bufferSize);
    }
    
    
    /******************************************************************/
    // Stream与byte
    /******************************************************************/
    
    // /**
    //  * 跳过输入流一部分
    //  */
    // public static void skip(InputStream input, long len) throws IOException {
    //     super
    //     while (len > 0) {
    //         long count = input.skip(len);
    //         if (count < 0) {throw new EOFException("按长度跳过流时,input 可读内容长度不足");}
    //
    //         len -= count;
    //     }
    // }
    
    /**
     * 读输入流到缓存中
     *
     * @param input  输入流
     * @param buffer 读取换从
     * @param off    其实位置
     * @param len    的读取长度
     * @return buffer
     * @throws IOException 异常信息
     */
    public static byte[] putBytes(InputStream input, byte[] buffer, int off, int len) throws IOException {
        while (len > 0) {
            int count = input.read(buffer, off, len);
            if (count < 0) {throw new EOFException("按长度读流时,input 可读内容长度不足");}
            
            off += count;
            len -= count;
            if(count == 0){
                break;
            }
        }
        return buffer;
    }
    
    /**
     * 读输入流到输出流中
     */
    public static long putBytes(InputStream input, OutputStream output) throws IOException {
        byte[] buffer = new byte[KiB];
        int len = 0;
        long count = 0;
        while ((len = input.read(buffer)) > 0) {
            output.write(buffer, 0, len);
            count += len;
        }
        return count;
    }
    
    /**
     * 读CLASSPATH中资源到输出流中
     */
    public static long putBytesClassPath(Class<?> clazz, String path, OutputStream output) throws IOException {
        return putBytes(clazz.getResourceAsStream(path), output);
    }
    
    /**
     * 指定长度读去流
     *
     * @param input 数据流
     * @param len   指定长度
     * @return 子节数组
     * @throws IOException 异常信息
     */
    public static byte[] getBytes(InputStream input, int len) throws IOException {
        if (len < 0) {return _EMPTY_B_;}
        byte[] buffer = new byte[len];
        putBytes(input, buffer, 0, len);
        return buffer;
    }
    
    /**
     * 读文件中字节数
     */
    public static byte[] getBytes(File file) throws IOException {
        if (file == null || !file.isFile() || !file.canRead()) {return null;}
        
        try (FileInputStream input = new FileInputStream(file)) {
            return getBytes(input);
        }
    }
    
    /**
     * 读文件中字节数
     */
    public static byte[] getBytes(InputStream input) throws IOException {
        try (InputStream in = input) {
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            putBytes(in, output);
            return output.toByteArray();
        }
    }
    
    /**
     * 读CLASSPATH中资源
     */
    public static byte[] getBytesClassPath(String path) throws IOException {
        InputStream input = Resources.getResourceStream(path);
        if (input == null) {return null;}
        
        try {
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            putBytes(input, output);
            return output.toByteArray();
        }
        finally {
            input.close();
        }
    }
    
    /**
     * 读CLASSPATH中资源
     */
    public static byte[] getBytesClassPath(Class<?> clazz, String path) throws IOException {
        InputStream input = clazz.getResourceAsStream(path);
        if (input == null) {input = Resources.getResourceStream(path);}
        
        if (input == null) {return null;}
        
        try {
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            putBytes(input, output);
            return output.toByteArray();
        }
        finally {
            input.close();
        }
    }
    
    /******************************************************************/
    // Stream读行和encoding
    /******************************************************************/
    
    /**
     * 读流获取一行，指定最大长度，偏移量指定为0，按顺序读取
     *
     * @param input 流
     * @param b     放进的缓存
     * @param len   支持的最大长度
     */
    public static int readLine(InputStream input, byte[] b, int len) throws IOException {
        return readLine(input, b, 0, len);
    }
    
    /**
     * 读流获取一行，指定偏移量和最大长度
     *
     * @param input 流
     * @param b     放进的缓存
     * @param off   流字节偏移量
     * @param len   支持的最大长度
     */
    public static int readLine(InputStream input, byte[] b, int off, int len) throws IOException {
        if (len <= 0) {return 0;}
        
        int count = 0, c;
        while ((c = input.read()) != -1) {
            b[off++] = (byte) c;
            count++;
            if (c == '\n' || count == len) {break;}
        }
        
        return count > 0 ? count : -1;
    }
    
    /**
     * 依次检测流的编码格式，该方法一定返回编码，如果有BOM则以BOM为准，否则判断是否是UTF-8，如果不是则是ANSI
     * <p>
     * 1.bytes有效长度<2取_UTF_8_ 2.有BOM，取BOM 3.逐个字节检查是否符合UTF-8，直到结尾 3.1 中途有不符合UTF-8的，返回系统编码Systems.getSystemEncoding(); 3.2
     * 到结尾都符合UTF-8，返回UTF-8
     *
     * @param bytes 字节数组
     * @param len   指定在bytes中有效的长度
     * @return 该方法一定返回编码
     */
    public static String getStreamEncoding(byte[] bytes, int len) {
        return getStreamEncoding(bytes, len, Systems.getSystemEncoding());
    }
    
    /**
     * 依次检测流的编码格式，该方法一定返回编码，如果有BOM则以BOM为准，否则判断是否是UTF-8，如果不是则是ANSI
     * <p>
     * 1.bytes有效长度<2取_UTF_8_ 2.有BOM，取BOM 3.逐个字节检查是否符合UTF-8，直到结尾 3.1 中途有不符合UTF-8的，返回缺省编码 3.2 到结尾都符合UTF-8，返回UTF-8
     *
     * @param bytes           字节数组
     * @param len             指定在bytes中有效的长度
     * @param defaultEncoding 缺省编码
     * @return 该方法一定返回编码
     */
    public static String getStreamEncoding(byte[] bytes, int len, String defaultEncoding) {
        if (bytes == null || bytes.length < 2 || len < 2) {
            return _UTF_8_;// null,0,1用UTF-8没问题
        }
        
        // 有BOM，以BOM为准
        String encoding = getStreamEncodingBOM(bytes);
        if (encoding != null) {return encoding;}
        
        // 没BOM，逐个字节检查是否是UTF-8
        if (bytes.length < len) {len = bytes.length;}
        
        // UTF-8 有以下编码规则：11100101 10001000 10011010表示"刚"字,01101001表示"i"
        // 如果高1位=0，表示这是一个 ASCII 字符（00 - 7F）。这里GBK,ISO8859-1,UTF-8相同。 01101001 表示 "i"
        // 如果高2位=10，表示它不是首字节，需要向前查找才能得到当前字符的首字节，一起组成一个字符。
        // 如果高2位=11，表示它是字符首字节
        // 1)高3位=110表示该字符由2个字节组成，后一个字节高2位=10，如俄文字符等。 11010000 10111011 表示 "л"
        // 2)高3位=111表示该字符由3个字节组成，后两个字节高2位=10，如中文字符等。 11100101 10001000 10011010 表示 "刚"
        //
        encoding = defaultEncoding == null ? Systems.getSystemEncoding() : defaultEncoding;
        byte firstChar = 0;
        int hasNum = 0;
        int num = 0;
        for (int i = 0; i < len; i++) {
            byte b = bytes[i];
            if (Bits.isZero(b, 1)) {// 高1位=0的ANSI
                
                // 测试时否满足UTF-8规范，不满足返回本地编码
                if (firstChar != 0 && hasNum != 0 && hasNum != num) {return encoding;}
                
                firstChar = 0;
                hasNum = 0;
                num = 0;
                continue;
            }
            
            // 高1位=1时，判断高第2位
            if (Bits.isZero(b, 2)) {
                // 高第2位是0,表示前面有首字节，如果没有则测试未通过，返回本地编码
                if (firstChar == 0) {return encoding;}
                
                // 通过个数++
                num++;
            }
            else {// 高第2位是1，表示首字节
                
                // 测试时否满足UTF-8规范，不满足返回本地编码
                if (firstChar != 0 && hasNum != 0 && hasNum != num) {return encoding;}
                
                // 否则测试通过，重新下一个
                firstChar = b;
                hasNum = (Bits.isZero(b, 3)) ? 1 : 2;
                num = 0;
            }
        }
        
        // 最后一个，测试时否满足UTF-8规范，不满足返回本地编码
        if (firstChar != 0 && hasNum != 0 && hasNum != num) {return encoding;}
        
        // 全部测试通过，返回UTF-8
        return _UTF_8_;
    }
    
    /**
     * 根据指定的流检查是否有BOM头的编码格式
     *
     * @param b 字节数组
     * @return UTF-8/UTF-16BE/UTF-16LE/UTF-32BE/UTF-32LE/如果都不是则为null
     */
    public static String getStreamEncodingBOM(byte[] b) {
        if (b == null || b.length < 2) {return null;}
        
        if (Bytes.isUTF8BOM(b)) {return _UTF_8_;}
        else if (Bytes.isUTF16BEBOM(b)) {return _UTF_16BE_;}
        else if (Bytes.isUTF16LEBOM(b)) {return _UTF_16LE_;}
        else if (Bytes.isUTF32BEBOM(b)) {return _UTF_32BE_;}
        else if (Bytes.isUTF32LEBOM(b)) {return _UTF_32LE_;}
        
        return null;
    }
}
